/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package org.simpla;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.app.Dialog;
import android.app.SearchManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Vibrator;
import android.provider.Settings.Secure;
import android.telephony.TelephonyManager;
import android.util.Log;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author Jacob Nordfalk
 */
public class Simpla {

  private Activity activity;

  public Activity getActivity() { return activity; }
  
	private static Simpla instance;

  // There is only one UI thread per VM, so this can be static
	private static Thread uiThread;

  // Hence, only one Handler should be required
	private static Handler handler;

	public static Simpla init(Activity activity) {
		if (activity == null) throw new NullPointerException("Simpla,init() must be initialized with with your activity object");
		if (instance == null) instance = new Simpla();

		instance.activity = activity;
		if (uiThread==null) {
				activity.runOnUiThread(new Runnable() {
					public void run() {
						uiThread = Thread.currentThread();
						handler = new Handler();
						if (handler.getLooper().getThread() != uiThread) throw new InternalError(handler.getLooper().getThread()+" != "+uiThread);
					}
				});
		}
		return instance;
	}

	public static Simpla getSimpla() {
		if (instance == null) throw new IllegalStateException("You must call Simpla.init() first with your activity. ");
		return instance;
	}

  public static void logException(Exception e) {
    e.printStackTrace();
    Log.e("Simpla", "exception", e);
  }

  public void showException(Exception e) {
		logException(e);
		showToast(e.getLocalizedMessage());
  }

  /**
   * Wait for
   * @param semaphore
   */
	public static void notify(Object semaphore) {
    Log.i("Simpla", "notify: "+ semaphore);
		synchronized (semaphore) {
			semaphore.notify();
		}
	}
/*
	public static void notifySimpla() {
		notify(instance);
	}

	public static void waitForNotifySimpla() {
		waitForNotify(instance);
	}
 */

  /**
   * Stop here and wait for notification on an object
   * @param semaphore The object to wait for notification on
   */
	public static void waitForNotify(Object semaphore) {
		if (uiThread == Thread.currentThread()) {
	    throw new IllegalThreadStateException(
          "Sorry, the main UI thread must not wait for other stuff to finish");
		}
    Log.i("Simpla", "waitForNotify"+ semaphore); //, new Exception());

		synchronized (semaphore) {
			try {
				semaphore.wait();
			} catch (InterruptedException ex) {
				logException(ex);
			}
		}
	}

  /**
   * Sleep for the given interval of time (given in milliseconds).
   * @param miliseconds The time to sleep in milliseconds.
   */
  public void sleep(int miliseconds) {
		try {
			Thread.sleep(miliseconds);
		} catch (InterruptedException ex) {
			logException(ex);
		}
	}


    /**
     * Enqueue the specified action to on the UI thread.
		 * The task will be executed as soon as possible, but if there are other tasks they will be finished first.<br/>
		 * The call returns immediately.
     *
     * @param action the action to run on the UI thread
     */
    public static void runOnUiThreadLater(Runnable action) {
				handler.post(action);
    }

    /**
     * Runs the specified action on the UI thread, waiting for the action to finish
		 * before continuing.
     *
     * @param action the action to run on the UI thread
     */
    public static void runOnUiThreadWaitForFinish(final Runnable action) {
        if (Thread.currentThread() != uiThread) {
						final Object semaphore = new Object();
					  Runnable r = new Runnable() {
							public void run() {
								action.run();
								synchronized (semaphore) {
									semaphore.notify();
								}
							}
						};
						synchronized (semaphore) {
	            handler.post(r);
							waitForNotify(semaphore);
						}
        } else {
            action.run();
        }
    }

	/**
	 * Shows an alert to the user. The user can select one of the buttons or press "back" key
	 * @param title Title of the alert
	 * @param button1Text
	 * @param button2Text
	 * @return button1Text or button2Text or null if the user press "back" key.
	 */
	public String showTwoButtonAlertDialog(final String title, final String button1Text, final String button2Text) {
		final Reference<String> reference = new Reference<String>();

		Runnable guiTtask = new Runnable() {
			public void run() {

        AlertDialog dialog=new AlertDialog.Builder(activity)
            .setTitle(title)
            .setNeutralButton(button1Text, new Dialog.OnClickListener() {
					public void onClick(DialogInterface arg0, int arg1) {
						reference.referent = button1Text;
						Simpla.notify(reference);
					}
				})
           .setNegativeButton(button2Text, new Dialog.OnClickListener() {
					public void onClick(DialogInterface arg0, int arg1) {
						reference.referent = button2Text;
						Simpla.notify(reference);
					}
				})
          .setOnCancelListener(new OnCancelListener() {
          public void onCancel(DialogInterface arg0) {
						Simpla.notify(reference);
          }
        })
          .create();
        dialog.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
        /*
        WindowManager.LayoutParams lp = dialog.getWindow().getAttributes();
        lp.dimAmount=0.0f;
        //lp.alpha=1.0f;
        dialog.getWindow().setAttributes(lp);*/
        dialog.show();
			}
		};
		runOnUiThreadLater(guiTtask);
		waitForNotify(reference);
		return reference.referent;
	}

	/**
	 * Shows an alert to the user. The user can select one of the buttons or press "back" key
	 * @param title Title of the alert
   * @param items List of items to show
	 * @return the index of the item chosen or -1 if the user press "back" key.
   */
	public int showSpinnerAlertDialogInt(final String title, final String... items) {
		final Reference<Integer> res = new Reference<Integer>();
    res.referent = -1;
		Runnable task = new Runnable() {
			public void run() {
				AlertDialog dialog=new AlertDialog.Builder(activity)
				.setTitle(title)
				.setItems(items, new Dialog.OnClickListener() {
					public void onClick(DialogInterface arg0, int which) {
						res.referent = which;
						Simpla.notify(res);
					}
				})
				.create();
        dialog.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
        dialog.show();
			}
		};
		runOnUiThreadLater(task);
		waitForNotify(res);
    return res.referent;
  }


	/**
	 * Shows an alert to the user. The user can select one of the buttons or press "back" key
	 * @param title Title of the alert
   * @param items List of items to show
	 * @return the item chosen or null if the user press "back" key.
   */
	public String showSpinnerAlertDialog(final String title, final String... items) {
    int res = showSpinnerAlertDialogInt(title, items);
    if (res>=0) return items[res];
    return null;
	}


	public String showEditTextDialog(final String title, final String initialValue) {
		final Reference<String> reference = new Reference<String>();
		Runnable task = new Runnable() {
			public void run() {
				Builder ab=new AlertDialog.Builder(activity);
				ab.setTitle(title);
				// ab.setMessage(title); enten  setTitle() eller setMessage(), ikke begge to.
				final EditText et = new EditText(activity);
				et.setText(initialValue);
        if (initialValue.length()>100) et.setTextSize(10);
				ab.setView(et);
				ab.setNeutralButton("OK", new Dialog.OnClickListener() {
					public void onClick(DialogInterface arg0, int arg1) {
						reference.referent = et.getText().toString();
						Simpla.notify(reference);
					}
				});
				ab.create().show();
			}
		};
		runOnUiThreadLater(task);
		waitForNotify(reference);

		System.err.println("endelig res.reference  = " + reference.referent );
		return reference.referent;
	}


	/**
	 * Prints (appends) a text to a TextView.
	 * It is safe to call this method even if the TextView is currently visible.
	 * If the view is embedded in a ScrollView its scrolled down so the appended text is visible.
	 * @param tv
	 * @param text
	 */
	public void print(final TextView tv, final String text) {
		runOnUiThreadLater(new Runnable() {
			public void run() {
				tv.append(text);
        // scroll to bottom - hvorfor var det her fjernet??
				if (tv.getParent() instanceof ScrollView) {
            ScrollView sv = (ScrollView) tv.getParent();
            sv.scrollTo(0, tv.getHeight());
				}
		  }
    });
	}


/**
        // scroll to bottom
				if (tv.getParent() instanceof ScrollView) {
          ScrollView sv = (ScrollView) tv.getParent();
          //sv.scrollTo(0, Integer.MAX_VALUE); - det ryster og tekst forsvinder
          // bedre:
          runOnUiThreadLater(new Runnable() {
            public void run() {
                ScrollView sv = (ScrollView) tv.getParent();
                sv.scrollTo(0, tv.getHeight());
            }
          });
				}
			}
*/
	/**
	 * Prints (appends) a text and a newline to a TextView.
	 * It is safe to call this method even if the TextView is currently visible.
	 * If the view is embedded in a ScrollView its scrolled down so the appended text is visible.
	 * @param tv
	 * @param s
	 */
	public void println(final TextView tv, final String s) {
    print(tv, s+"\n");
  }

	private static PrintStream originalSystemOut = null;

	/**
	 * Appends all output written to System.out to a given TextView. <br/>
	 * This is handy for showing output of simple programs logging and for logging.<br/>
	 * Call <code>systemOutToTextView(null)</code> to stop appending.
	 * @param tv The view to print to. If null this cancels previous redirections.
	 */
	public void systemOutToTextView(final TextView tv) {

		if (originalSystemOut == null) originalSystemOut = System.out;
		if (tv == null) {
			System.setOut(originalSystemOut);
			return;
		}

		System.setOut(new PrintStream(new OutputStream() {
			public void write(final int arg0)  {
				print(tv, Character.toString((char) arg0));
			}

			public void write(final byte[] b, final int off, final int len)  {
				print(tv, new String(b, off, len));
			}

			public void write(byte[] b)  {
				write(b, 0, b.length);
			}
		}));
	}


  public void switchActivity() {
    PackageManager pm = activity.getPackageManager();

    // Find all activities in the package
    ArrayList<String> labels = new ArrayList<String>();
    ArrayList<String> classNames = new ArrayList<String>();


    try {
      PackageInfo pi = pm.getPackageInfo(activity.getPackageName(), PackageManager.GET_ACTIVITIES);
      for (ActivityInfo ai : pi.activities) {
        labels.add(ai.loadLabel(pm).toString());
        classNames.add(ai.name);
      }
    } catch (NameNotFoundException ex) {
      Logger.getLogger(Simpla.class.getName()).log(Level.SEVERE, null, ex);
    }


    int selected = showSpinnerAlertDialogInt("Select an activity", labels.toArray(new String[labels.size()]));

    if (selected>=0) try {
      Class clazz = Class.forName( classNames.get(selected));
      Intent i = new Intent(activity, clazz);
      Activity presentActivity = activity;
      startActivity(i);
  		waitForNotify(presentActivity);
      activity = presentActivity;
    } catch (ClassNotFoundException ex) {
      ex.printStackTrace();
    }
  }




  private View lastClicked = null;
  
  /**
   * @return The lastClicked button or view
   */
  public View getLastClicked() {
    return lastClicked;
  }

  public void setNotifyOnClick(final View button, final Object semaphore) {
    // TODO: Fjern!
    button.setOnClickListener(new Button.OnClickListener() {
      public void onClick(View view) {
        lastClicked = view;
				Simpla.notify(semaphore);
      }
    });
  }

	private static class Reference<E> {
		public E referent;
	}


	/** 
	 * Creates an SMS intent. Call startActivity() with the intent to activate it.
	 * @param number
	 * @param message
	 */
  public Intent createSmsIntent(String number, CharSequence message) {
		// Source: http://andmobidev.blogspot.com/2010/01/launching-smsmessages-activity-using.html
      Intent intent=new Intent(Intent.ACTION_VIEW);
      //Intent intent=new Intent(Intent.ACTION_VIEW, Uri.parse("sms:"+number));
      intent.setType("vnd.android-dir/mms-sms");
      intent.putExtra("sms_body", String.valueOf(message));
			intent.putExtra("address", number);
			return intent;
	}


  /**
   * Wait for another activity to finish
   */
	public void waitForActivityToFinish() {
		waitForNotify(activity);
	}

	/** Opens an SMS window and waits for the user to send it.
	 * Returns When the user presses the 'back' button.
	 * @param number
	 * @param message
	 */
	public void sendSms(String number, CharSequence  message) {
    Intent intent= createSmsIntent(number, message);
    startActivity(intent);
    waitForActivityToFinish();
	}



  public void vibrate(int milliseconds) {
    try {
      ((Vibrator) activity.getSystemService(Context.VIBRATOR_SERVICE)).vibrate(milliseconds);
    } catch (Exception e) {
      showException(e);
    }
  }

  /**
   * Makes a web search and waits for the user to finish
   * @param searchString
   */
  public void webSearch(String searchString) {
    Intent searchGivenText=new Intent(Intent.ACTION_WEB_SEARCH);
    searchGivenText.putExtra(SearchManager.QUERY, searchString);
    startActivity(searchGivenText);
		waitForActivityToFinish();
  }

  /**
   * Opens a URL and waits for the user to finish
   * @param url
   */
  public void openUrl(String url) {
    Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
    startActivity(intent);
		waitForActivityToFinish();
  }


  void openSendEmail(String emne, String txt) {
      Intent emailIntent = new Intent(android.content.Intent.ACTION_SEND);
      emailIntent.setType("plain/text");
      emailIntent.putExtra(android.content.Intent.EXTRA_EMAIL, new String[]{"jacob.nordfalk@gmail.com"});
      emailIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, emne);
      emailIntent.putExtra(android.content.Intent.EXTRA_TEXT, txt);
      activity.startActivity(Intent.createChooser(emailIntent, "Send mail..."));
  }



  public void showToast(final String besked) {
		runOnUiThreadLater(new Runnable() {
			public void run() {
				Toast toast=Toast.makeText(activity, besked, Toast.LENGTH_LONG);
				toast.show();
			}
		});
  }

	private RuntimeException exception = null;

	/**
	 * Shows the view. The view can be simple or composed with a group of views or a layout.<br/>
	 * <strong>note:</strong>While the view is visible its content must not be changed.
	 * If you want to change its content then use <code>setContentView(null)</code>
	 *
	 * @param view The desired content to display.
	 */
	public void setContentView(final View view) {
		exception = null;
		Runnable task = new Runnable() {
			public void run() {
				try {
					if (view != null) activity.setContentView(view);
					else activity.setContentView(new View(activity));
				} catch (RuntimeException e) { exception = e; }
			}
		};
		runOnUiThreadWaitForFinish(task);
		if (exception != null) throw new RuntimeException(exception);
	}

	public TextView createTextView(String string) {
		TextView tv = new TextView(activity);
		if (string != null) tv.setText(string);
		return tv;
	}

	public EditText createEditText(String string) {
		EditText tv = new EditText(activity);
		if (string != null) tv.setText(string);
		return tv;
	}

  /**
   * Henter bitmap fra URL.
   * Bør ikke kaldes fra GUI-tråden, da operationen kan tage tid
   * @param url Adressen på billedet
   * @return Billedet eller null
   */
  public static Bitmap createBitmapFromUrl(String url) {
    try {
      InputStream is = new URL(url).openStream();
      Bitmap bitmap=BitmapFactory.decodeStream(is);
      is.close();
      return bitmap;
    } catch (IOException ex) {
      logException(ex);
      return null;
    }
  }


	/**
	 * Starts an activity (a new 'window' overlapping this window).
	 * The method returns immediately.
	 * @param intent
	 */
  public void startActivity(Intent intent) {
    activity.startActivity(intent);
  }

	/**
	 * Gives an unique ID for the phone. See http://strazzere.com/blog/?p=113
	 * @return The Android ID (a unique 64-bit value) as a hex string.
   * Before Android 2.2 null would be returned if running in an emulator.
	 */
	public String getUniqueDeviceId() {
		return Secure.getString(activity.getContentResolver(), Secure.ANDROID_ID);
	}

	/**
	 * Get the IMEI unique ID for the phone.
	 * See http://www.androidsoftwaredeveloper.com/2009/04/02/how-to-get-the-phone-imei/
	 * Don't forget to put this in AndroidManifest.xml:
	 * <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
	 * @return Returns the unique device ID, for example, the IMEI for GSM and the MEID for CDMA phones. Return null if device ID is not available.
	 */
	public String getIMEI() {
		TelephonyManager tm = (TelephonyManager) (activity.getSystemService(Context.TELEPHONY_SERVICE));
		String imei = tm.getDeviceId();
		return imei;
	}



	/**
	 * Determines if the app is running in an emulator.
	 * This is nice to know e.g. when you use Google maps and needs to decide
	 * whether to use a debug certificate key, or your signed release key
	 * @return true if running in the emulator, otherwise false
	 */
	public boolean isRunningInEmulator() {
    // TODO Brug en anden metode!
		return (getUniqueDeviceId()==null);
	}



	public Location getLocation() {
		final LocationManager locationManager = (LocationManager) activity.getSystemService(Context.LOCATION_SERVICE);
		List<String> providers=locationManager.getProviders(true);

		System.err.println("providers = " + providers);

		if (providers.isEmpty()) return null;

		final Reference<Location> reference = new Reference<Location>();

		locationManager.requestLocationUpdates(providers.get(0),1, 1, new LocationListener() {
			public void onLocationChanged(Location location) {
				reference.referent = location;
				Simpla.notify(reference);
				locationManager.removeUpdates(this);
			}
			public void onStatusChanged(String arg0, int arg1, Bundle arg2) {
				Simpla.notify(reference);
			}
			public void onProviderEnabled(String arg0) {}
			public void onProviderDisabled(String arg0) {
				Simpla.notify(reference);
			}
		});
		Simpla.waitForNotify(reference);
		return reference.referent;

	}
}


/* unused stuff
	public Geocoder createGeoCoder() {
		Geocoder geoCoder = new Geocoder(activity, Locale.getDefault());
		return geoCoder;
	}

	public Canvas lockCanvas(SurfaceView sv) {
		 Canvas c = sv.getHolder().lockCanvas();
		 Log.d("Simpla", "lockCanvas 1: c = "+c);
		 if (c != null) return c;


			// according to http://developer.android.com/referent/android/view/SurfaceView.html
			// the canvas must be visible to be created. So we will just have to wait for it
			sv.getHolder().addCallback(
			new SurfaceHolder.Callback() {
				public void surfaceCreated(SurfaceHolder arg0) {
					Simpla.notify(activity);
				}
				public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {}
				public void surfaceDestroyed(SurfaceHolder arg0) {}
			});

			synchronized (activity) {
				setContentView(sv);
				c = sv.getHolder().lockCanvas();
				if (c != null) return c;
				Simpla.waitForNotify(activity);
				c = sv.getHolder().lockCanvas();
				if (c != null) return c;
				throw new IllegalStateException("Canvas object could not be retrieved!?");
			}
	}

	public SurfaceView createSurfaceView() {
		// return new SurfaceView(activity);

		// SurfaceView must be created by the UI thread

		final Reference<SurfaceView> ref = new Reference<SurfaceView>();

		Runnable task = new Runnable() {
			public void run() {
				ref.referent = new SurfaceView(activity);
				Simpla.notify(ref);
			}
		};
		runOnUiThreadWaitForFinish(task);
		return ref.referent;
	}
*/






        /*
		simpla.showTwoButtonAlertDialog("vis 0 ", "ja", "nej");
		simpla.showTwoButtonAlertDialog("vis getUniqueDeviceId " + simpla.getUniqueDeviceId(), "ja", "nej");

		simpla.showTwoButtonAlertDialog("vis getIMEI " + simpla.getIMEI(), "ja", "nej");
*/
//		LocationManager locationManager = (LocationManager)getSystemService(Context.LOCATION_SERVICE);
//		Location location = locationManager.getCurrentLocation("gps");


		/*
		final SurfaceView sv = simpla.createSurfaceView();
		simpla.setContentView(sv);
		final Canvas c = simpla.lockCanvas(sv);
		Paint mLinePaint = new Paint();
		mLinePaint.setAntiAlias(true);
		mLinePaint.setARGB(255, 0, 255, 0);
		c.drawRect(10,10, 30, 30, mLinePaint);
		c.drawText("Hej paint", 30, 30, mLinePaint);
		sv.getHolder().unlockCanvasAndPost(c);
		simpla.sleep(5000);


		Geocoder gc = simpla.createGeoCoder();
		//List<Address> adr = gc.getFromLocationName("Carl Langes Vej 38, Valby", 5);
		List<Address> adr = gc.getFromLocationName("1600 Amphitheatre Parkway, Mountain View, CA", 5);
		System.err.println("adr = " + adr);
		for (Address a : adr) {
			System.err.println("a = " + a);
		}
		System.out.println("HEJ");
		 */

